﻿#region License
/**	
The MIT License (MIT)

Copyright (c) 2013 Philipp Schillinger, Michael Alekseew, Andreas Braun, Steeven Zeiß

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. 
 **/
#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;

using Emgu.CV;
using Emgu.CV.Structure;
using Emgu.CV.GPU;
using Emgu.CV.VideoSurveillance;

namespace IndoorLocalization.Detection
{
	/// <summary>
	/// The motion detection class can be used to detect the motion in camera frames.
	/// </summary>
	class MotionDetection {

		// currently unused
        private const int REF_FRAME_DELAY_SEC = 5;      // After a delay of 5 sec without detection the reference picture will be updated
		
		// currently unused
        private static double moveFactor = 0.0;         // Move factor how "fast" the reference picture should adapt the current frame
        
		/// <summary>
		/// Detects the Motion in the given image (difference to a reference picture)
		/// and gives a list of rays back that point do the center of area of the motion.
		/// It sets also the transformed current frame, the motion detection frame and the calculated difference frame.
		/// </summary>
		/// <param name="cam">The camera for which the motion detection should be calculated.</param>
		/// <param name="transFrame">The reference to the transformed current frame that should be set.</param>
		/// <param name="motionFrame">The reference to the motion detection frame that should be set.</param>
		/// <param name="calcFrameGray">The reference to the calculated difference frame that should be set.</param>
		/// <returns>A list of all rays that point in the direction where the motion was detected.</returns>
		public static List<SensorRay> DetectMotion(SensorCam cam, ref Image<Bgr, Byte> transFrame, ref Image<Bgr, Byte> motionFrame, ref Image<Gray, Byte> calcFrameGray) {
			// get current frame from the camera
            Image<Bgr, Byte> curFrame = cam.Frame;

			// warp the perspective view of the camera with the help of the transformation matrix that depends on the current pitch
			Image<Bgr, Byte> tmpTransFrame = curFrame.WarpPerspective(cam.TransMatrix, Program.FrameWidth, Program.FrameHeight, Emgu.CV.CvEnum.INTER.CV_INTER_LINEAR, Emgu.CV.CvEnum.WARP.CV_WARP_FILL_OUTLIERS, new Bgr(0, 0, 0));

            // convert current frame to gray frame
            Image<Gray, Byte> curFrameGray = tmpTransFrame.Convert<Gray, Byte>();

			// set the transformed frame
			transFrame = tmpTransFrame;

            // add weighted for refFrameGray
            cam.RefFrameGray = cam.RefFrameGray.AddWeighted(curFrameGray, 1 - moveFactor, moveFactor, 0.0);

            // calculate absolute difference between current frame gray and reference frame gray
            Image<Gray, Byte> diffFrameGray = curFrameGray.AbsDiff(cam.RefFrameGray);

            // take only the pixels which color is between threshold and 255
            Image<Gray, Byte> tmpCalcFrameGray = diffFrameGray.ThresholdBinary(new Gray(Program.PrepThreshold), new Gray(255));

            // do the erode (reduce noise)
			tmpCalcFrameGray._Erode(Program.PrepErode);

            // do the dilate (make bigger white areas)
			tmpCalcFrameGray._Dilate(Program.PrepDilate);

            // copy only the pixels where calcFrameGray is white
			motionFrame = transFrame.Copy(tmpCalcFrameGray);

			// set the calculated frame
			calcFrameGray = tmpCalcFrameGray;

            // detects the contours
            #region Contour-Detection
            List<Rectangle> boxList = new List<Rectangle>();
            MemStorage store = new MemStorage();
            Contour<Point> contours = calcFrameGray.FindContours(Emgu.CV.CvEnum.CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE, Emgu.CV.CvEnum.RETR_TYPE.CV_RETR_LIST, store);
            for (; contours != null; contours = contours.HNext) {
                int minWidth = (int)(Program.FrameWidth * Program.MinRelRectWidth);
                int minHeight = (int)(Program.FrameHeight * Program.MinRelRectHeight);
                if (contours.Area > minWidth * minHeight * 0.5) {
                    Rectangle rect = contours.BoundingRectangle;
                    if (rect.Width > minWidth && rect.Height > minHeight)
						boxList.Add(rect);
                }
            }
            #endregion

            // merge boxes if the are over each other
            #region Merge overlapping Boxes
            if (boxList.Count > 1) {
                bool changed;
                do {
                    changed = false;
                    for (int i = 0; i < boxList.Count - 1; i++) {
                        Rectangle rect = boxList[i];
                        for (int j = i + 1; j < boxList.Count; j++) {
                            Rectangle refRect = boxList[j];
                            if (rect.IntersectsWith(refRect)) {
                                int minX = Math.Min(rect.X, refRect.X);
                                int maxX = Math.Max(rect.X + rect.Width, refRect.X + refRect.Width);
                                int minY = Math.Min(rect.Y, refRect.Y);
                                int maxY = Math.Max(rect.Y + rect.Height, refRect.Y + refRect.Height);
                                rect.X = minX;
                                rect.Y = minY;
                                rect.Width = maxX - minX;
                                rect.Height = maxY - minY;
                                boxList[i] = rect;
                                boxList.Remove(refRect);
                                changed = true;
                                break;
                            }
                        }
                    }
                } while (changed);
            }
            #endregion

            // filter min x-coordinates of centers of rectangles
            #region Filter Boxes x-Distance
            for (int i = 0; i < boxList.Count - 1; i++) {
                Rectangle rect_i = boxList[i];
                for (int j = i+1; j < boxList.Count; j++) {
                    Rectangle rect_j = boxList[j];
                    if (Math.Abs(rect_i.X + rect_i.Width / 2 - rect_j.X - rect_j.Width / 2) < Program.MinRectXDistance) {
                        int minX = Math.Min(rect_i.X, rect_j.X);
                        int maxX = Math.Max(rect_i.X + rect_i.Width, rect_j.X + rect_j.Width);
                        int minY = Math.Min(rect_i.Y, rect_j.Y);
                        int maxY = Math.Max(rect_i.Y + rect_j.Height, rect_j.Y + rect_j.Height);
                        rect_i.X = minX;
                        rect_i.Y = minY;
                        rect_i.Width = maxX - minX;
                        rect_i.Height = maxY - minY;
                        boxList[i] = rect_i;
                        boxList.Remove(rect_j);
                        j--;
                    }
                }
            }
            #endregion

			// create the list of all rays that point to the place where the motion was detected
            List<SensorRay> rays = new List<SensorRay>();
            foreach (Rectangle rect in boxList) {
                // calculate in which direction the motion detection is (seen from the cam)
                float direction = (1 - (2.0f * rect.X + rect.Width) / Program.FrameWidth) * Program.CamAperture / 2;
				SensorRay tmpRay = new SensorRay(direction, cam);

				// calculate the heuristic for the distance of the detected object to the camera
				double frameSize = Math.Sqrt(Program.FrameWidth * Program.FrameHeight);
				double tmpVal = -((Math.Sqrt(rect.Width * rect.Height) - 0.01 * frameSize) / (0.6 * frameSize - 0.01 * frameSize)) * 10;
				tmpRay.EstimatedDistanceIndicator = (int)Math.Round(10 + Math.Min(Math.Max(tmpVal, -10), 0));

                
                motionFrame.Draw(new CircleF(new PointF((float)(rect.X + rect.Width / 2), (float)(rect.Y + rect.Height / 2)), 2.0f), new Bgr(Color.GreenYellow), 8);
                motionFrame.ROI = rect;
                Bgr boxColor;
                DenseHistogram hist = new DenseHistogram(256, new RangeF(0.0f, 255.0f));
                MCvScalar tempScl;
                motionFrame.AvgSdv(out boxColor, out tempScl);
                hist.Calculate(new Image<Gray, Byte>[] {motionFrame[0]}, true, null);
                CvInvoke.cvResetImageROI(motionFrame);
                motionFrame.Draw(rect, boxColor, 3);

                tmpRay.AvgColor = boxColor;
                rays.Add(tmpRay);
            }
            return rays;
		}

		/// <summary>
		/// Move factor how "fast" the reference picture should adapt the current frame.
		/// Set to 0.0 per default because currently unused.
		/// </summary>
        public static double MoveFactore {
            get { return moveFactor; }
            set { moveFactor = value; }
        }

	}
}
